package cn.rengy.auth.pathtree;


import cn.rengy.lang.ResultEntity;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;

/**
 *把uri解析成tree形
 */
@Slf4j
public class PathTree {

    private static final String URL_PATH_SPLIT = "/";
    private static final String MATCH_ALL_METHOD = "ALL";
    private static final String MATCH_ONE_PATH = "*";
    private static final String MATCH_MULTI_PATH = "**";
    private static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/+");

    /**
     * root node
     */
    private Node root;

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private Lock readLock = readWriteLock.readLock();
    private Lock writeLock = readWriteLock.writeLock();
    PathTree() {
        this.root = new Node("root");
    }

    /**
     * 必须同步访问
     * build dictionary matching tree
     * @param paths resource path set
     */
     public void buildTree(Collection<UriPath> paths) {
         writeLock.lock();
         try {
            if (log.isDebugEnabled()) {
                log.debug("start buildPathTree");
            }
            clearTree();
            for (UriPath path : paths) {
                insertNode(path, this.root);
            }
            getResourceNum();
            if (log.isDebugEnabled()) {
                log.debug("buildPathTree finish.");
            }
        }catch (Exception e){
            throw e;
        }finally {
             writeLock.unlock();
        }

    }



    /**
     * clear dictionary matching tree
     */
     public void clearTree() {
        log.info("clearTree");
        Map<String, Node> children=root.getChildren();
        if(children!=null){
            children.clear();
        }
    }

    /**
     * Get the number of resources (URL+METHOD) in the current matching tree
     * @return int resource number
     */
    private int getResourceNum() {
        int resourceNum = 0;
        // Breadth First Search - bfs
        Queue<Node> resourceList = new LinkedList<>();
        resourceList.add(root);
        while (!resourceList.isEmpty()) {
            Node currentNode = resourceList.poll();
            if (currentNode.getMethodMap() != null && !currentNode.getMethodMap().isEmpty()) {
                resourceNum += currentNode.getMethodMap().size();
            }
            if (currentNode.getChildren() != null && !currentNode.getChildren().isEmpty()) {
                resourceList.addAll(currentNode.getChildren().values());
            }
        }
        log.info("current PathTree resource num is: {}", resourceNum);
        return resourceNum;
    }

    /**
     * 匹配uri
     * @param path   /api/v2/host/detail===get
     */
     public ResultEntity<MatchResult,List<String>> searchUri(String path, String method) {
         readLock.lock();
         try {
             if (path == null || "".equals(path)) {
                 return null;
             }
             if (log.isTraceEnabled()) {
                 log.trace(" - searchPathFilterRoles, path is {}", path);
             }
             if (!path.startsWith(URL_PATH_SPLIT)) {
                 path = URL_PATH_SPLIT.concat(path);
             }
             path = PATH_SPLIT_PATTERN.matcher(path).replaceAll("/");
             path = path.substring(1).toLowerCase();
             //String[] tmp = path.split("===");
             //if (tmp.length != PATH_NODE_NUM_2) {
             //    return null;
             //}
             String[] urlPac = path.split("/");
             //String method = tmp[1];

             // Pattern matching   * **
             Node current = root;
             return search(current, urlPac, 0,method);
         }catch (Exception e){
            throw e;
         }finally {
             readLock.unlock();
         }
    }


    public enum MatchResult{
        notFound,//未找到
        anon,//匿名访问
        methodNotSupported,//method不支持
        authen,//登录后访问
        author//指定角色访问
    }

    /**
     * Match the next node from the current node
     * @param current current node
     * @param urlPac urlPath arr
     * @param currentFlow current flow
     * @return match return roles eg:[role,role2], else return null
     */
    private ResultEntity<MatchResult,List<String>> search(Node current, String[] urlPac, int currentFlow, String method) {
        Map<String, Node> children=current.getChildren();
        //ResultEntity resultEntity = ResultEntity.code(MatchResult.notFound);
        if(children==null){
            return ResultEntity.ok(MatchResult.notFound,null);
        }
        String currentPart = urlPac[currentFlow];
        boolean matching=false;
        if (children.containsKey(currentPart)){
            matching=true;
        }else if (children.containsKey(MATCH_ONE_PATH)){
            matching=true;
            currentPart=MATCH_ONE_PATH;
        }else if (children.containsKey(MATCH_MULTI_PATH)){
            //TODO **可以代表多级
            matching=true;
            currentPart=MATCH_MULTI_PATH;
        }
        if(!matching){
            return ResultEntity.ok(MatchResult.notFound,null);
        }
        if(currentFlow == urlPac.length - 1//url最后一个节点
                ||currentPart.equals(MATCH_MULTI_PATH)){
            Node node=children.get(currentPart);
            Map<String, UriPath> methodMap=node.getMethodMap();
            /**
             * 当请求 /file/upload 来匹配
             * 如果只配置了 /file/upload/* 则会报错，因为 只有子节点才会有method
             * 所以需要同时配置/file/upload 和 /file/upload/*
             * **/
            if(methodMap==null){
                throw new RuntimeException("methodMap为空");
            }
            UriPath uriPath=methodMap.get(method);
            if(uriPath==null){
                uriPath=methodMap.get(MATCH_ALL_METHOD);
            }
            if(uriPath==null){
                //method不支持
                return ResultEntity.ok(MatchResult.methodNotSupported,null);
            }else{
                //匹配
                String authType=uriPath.getAuthType();//'anon','authen','author'
                MatchResult matchResult=MatchResult.valueOf(authType);
                return ResultEntity.ok(matchResult,uriPath.getRoles());
            }
        }else{
            return search(children.get(currentPart), urlPac, ++currentFlow,method);
        }
    }

//    private boolean isNoMatchString(String pattern, String pathNode) {
//        if (pattern == null && pathNode == null) {
//            return false;
//        }
//        if (pattern == null || pathNode == null) {
//            return true;
//        }
//        if (pattern.equals(pathNode) || MATCH_ONE_PATH.equals(pattern)
//                || MATCH_MULTI_PATH.equals(pattern)) {
//            return false;
//        }
//        if (!isPatternStr(pattern)) {
//            return true;
//        }
//        Pattern matcher = PATTERN_MAP.get(pattern);
//        if (matcher == null) {
//            String patternStr = makeJavaPatternStr(pattern);
//            matcher = Pattern.compile(patternStr);
//            PATTERN_MAP.put(pattern, matcher);
//        }
//        return !matcher.matcher(pathNode).matches();
//    }

//    private boolean isPatternStr(String str) {
//        return str != null
//                && str.contains(MATCH_ONE_PATH)
//                && !MATCH_ONE_PATH.equals(str)
//                && !MATCH_MULTI_PATH.equals(str);
//    }

//    private String makeJavaPatternStr(String pattern) {
//        return pattern
//                .replace("*", "\\S*")
//                .replace(".", "\\.")
//                .replace("?","\\?")
//                .replace("+", "\\+")
//                .replace("$", "\\$")
//                .replace("^","\\^");
//    }

    /**
     * insert pathNode
     * @param uri path = /api/v1/host/detail===GET===[role2,role3,role4]
     */
    private void insertNode(UriPath uri, Node rootNode) {
        String path=uri.getUri();
        String method=uri.getMethod();
        if (path == null || "".equals(path)) {
            return;
        }
        if (log.isTraceEnabled()) {
            log.trace("begin insertNode, path is {}", path);
        }
        if (!path.startsWith(URL_PATH_SPLIT)) {
            path = URL_PATH_SPLIT.concat(path);
        }
        path = PATH_SPLIT_PATTERN.matcher(path).replaceAll("/");
        // remove the first /
        path = path.substring(1);
        path = path.toLowerCase();
        String[] urlPac = path.split(URL_PATH_SPLIT);
        Node current = rootNode;
//        Node pre = current;
        // start inserting URL node
        for (String urlData : urlPac) {
            Map<String, Node> children=current.getChildren();
            if(children==null){
                children=current.setChildren(new LinkedHashMap<>(8,1));
            }
            if (!children.containsKey(urlData)) {
                children.put(urlData, new Node(urlData));
                // if urlData is match *xxx, insert patternChildren eg: *html
//                if (isPatternStr(urlData)) {
//                    Pattern pattern = PATTERN_MAP.get(urlData);
//                    if (pattern == null) {
//                        String patternStr = makeJavaPatternStr(urlData);
//                        pattern = Pattern.compile(patternStr);
//                        PATTERN_MAP.put(urlData, pattern);
//                    }
//                    current.insertPatternChild(urlData);
//                }
            }
//            pre = current;
            current = children.get(urlData);
        }

        //if (MATCH_ONE_PATH.equals(current.getData()) || MATCH_MULTI_PATH.equals(current.getData())) {
            // When the last one is * or **, it may match empty,
            // and the previous one may also be NODE_TYPE_MAY_PATH_END type
        //}
        // set node type is NODE_TYPE_MAY_PATH_END
        // start insert httpMethod method, if existed, not overwrite and modify the original configuration
        Map<String, UriPath> methodMap=current.getMethodMap();
        if(methodMap==null){
            methodMap=current.setMethodMap(new LinkedHashMap<>(1,1));
        }
        if (methodMap.containsKey(method)) {
            log.warn("The path resource: {} has match same resource config, ignore this one.", path);
            return;
        }
        if (current.getMethodMap().containsKey(MATCH_ALL_METHOD)) {
            log.warn("The path resource: {} has match same resource config(* means all http method), ignore this one.", path);
            return;
        }
        methodMap.put(method,uri);

    }

    /**
     * Dictionary matching tree node
     */
    private static class Node {


        /** current node data **/
        private String data;

        /** children nodes **/
        private Map<String, Node> children;

        /** pattern children list **/
        //private List<String> patternChildren;

        /** 根节点的method集合 **/
        private Map<String, UriPath> methodMap;

        private Node(String data) {
            this.data = data;
        }


        public Map<String, UriPath> getMethodMap() {
            return methodMap;
        }

        public Map<String, UriPath> setMethodMap(Map<String, UriPath> methodMap){
            this.methodMap=methodMap;
            return methodMap;
        }

        private String getData() {
            return data;
        }


        private Map<String, Node> getChildren() {
            return children;
        }

        private Map<String,Node> setChildren(Map<String,Node> children){
            this.children = children;
            return children;
        }



    }
}
